﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;

namespace MediaData
{
    public abstract class MediaImporter
    {
        private object completionLock = new object();
        protected MediaRootManager roots;
        public bool ImportInProgress { get; private set; }
        public List<MediaItemScanner> Scanners { get; private set; }

        public MediaImporter(MediaRootManager roots)
        {
            this.roots = roots;
            Scanners = new List<MediaItemScanner>();
        }

        protected abstract bool ItemExists(MediaItem item);
        protected abstract void AddItem(MediaItem item);
        protected abstract void RemoveItem(MediaItem item);
        protected abstract IEnumerable<string> GetExistingItems(string volume);

        public event EventHandler BeginCommit;
        public event EventHandler EndCommit;
        public event EventHandler<ImportEventArgs> BeginImport;
        public event EventHandler<ImportEventArgs> EndImport;


        public void ScanVolumes(IEnumerable<MediaRoot> volumeRoots, bool block)
        {
            // lock the workerDictionary 
            lock (completionLock)
            {
                // Foreach root
                foreach (var root in volumeRoots)
                {

                    // check if the import is already running
                    // if it is, ignore the request
                    if (workers.ContainsKey(root.Volume))
                        continue;

                    // else add the import to the dictionary
                    Worker worker = new Worker(root);
                    workers.Add(root.Volume, worker);

                    // start it asynchronously with a target root
                    Thread thread = new Thread(new ParameterizedThreadStart(BeginScanVolume));
                    thread.IsBackground = true;
                    thread.Priority = ThreadPriority.BelowNormal;

                    if (BeginImport != null)
                    {
                        BeginImport(this, new ImportEventArgs(worker.Root));
                    }
                    ImportInProgress = true;
                    thread.Start(worker);
                }
                if (block)
                {
                    while (block && ImportInProgress)
                    {
                        Monitor.Wait(completionLock);
                    }
                }
                else if(!ImportInProgress)
                {
                    CommitWorkers();
                }

            } // unlock the completion
        }

        public void ScanVolumes(IEnumerable<MediaRoot> volumeRoots)
        {
            ScanVolumes(volumeRoots, false);
        }

        public class ImportEventArgs : EventArgs
        {
            public MediaRoot Root { get; set; }
            public int Added { get; set; }
            public int Removed { get; set; }
            public ImportEventArgs(MediaRoot root)
            {
                this.Root = root;
            }
        }

        #region Workers

        private Dictionary<string, Worker> workers = new Dictionary<string, Worker>();

        private void BeginScanVolume(object payLoad)
        {
            Worker worker = payLoad as Worker;

            // foreach scanner
            foreach (var scanner in Scanners)
            {
                if (Directory.Exists(Path.Combine(worker.Root.Path, scanner.Type)))
                {
                    // scan the type directory
                    scanner.Scan(worker.Root, ItemExists, ScannerAddItem);
                }
            }

            // delete existing items
            foreach (var file in GetExistingItems(worker.Root.Volume))
            {
                if (!File.Exists(Path.Combine(worker.Root.Path, file)))
                    worker.DeletedItems.Add(new MediaItem(worker.Root.Volume, file));
            }

            // lock completion
            lock (completionLock)
            {
                worker.Complete = true;

                if (EndImport != null)
                    EndImport(this, new ImportEventArgs(worker.Root) { Added = worker.AddedItems.Count, Removed = worker.DeletedItems.Count });

                // is everyone done ?
                foreach (var w in workers.Values)
                {
                    // someone is not done, then we stop this thread
                    if (!w.Complete)
                        return;
                }
                // at this point all workers have ended and only one thread will be calling this method
                CommitWorkers();
            }

            

        }

        private void ScannerAddItem(MediaItem item)
        {
            DriveInfo di = new DriveInfo(item.Path);
            if (workers.ContainsKey(di.VolumeLabel))
            {
                if (string.IsNullOrEmpty(item.Type))
                    throw new ArgumentException("Media item did not have a type, it is required for TypeScanners to tag items with a type");

                if (string.IsNullOrEmpty(item.Title))
                    throw new ArgumentException("Media item did not have a title, it is required for TypeScanners to tag items with a title");

                FileInfo fi = new FileInfo(item.Path);
                item.Volume = di.VolumeLabel;
                item.CreationTime = fi.CreationTime;

                workers[item.Volume].AddedItems.Add(item);
            }
        }

        private void CommitWorkers()
        {
            if (BeginCommit != null)
                BeginCommit(this, EventArgs.Empty);

            // foreach worker
            foreach (var worker in workers.Values)
            {
                // add all data
                foreach (var item in worker.AddedItems)
                {
                    AddItem(item);
                }

                // delete all deletes
                foreach (var item in worker.DeletedItems)
                {
                    RemoveItem(item);
                }

            }
            // clear worker table
            workers.Clear();
            ImportInProgress = false;
            if (EndCommit != null)
                EndCommit(this, EventArgs.Empty);
            Monitor.PulseAll(completionLock);

        }

        internal class Worker
        {
            internal bool Complete { get; set; }
            internal MediaRoot Root { get; set; }

            internal List<MediaItem> AddedItems = new List<MediaItem>();
            internal List<MediaItem> DeletedItems = new List<MediaItem>();
            public Worker(MediaRoot root)
            {
                this.Root = root;
            }
        }
        #endregion

    }
}
